package com.stars.easyms.schedule.service;

import com.stars.easyms.schedule.bean.*;
import com.stars.easyms.schedule.constant.DistributedScheduleConstants;
import com.stars.easyms.schedule.enums.RoutePolicy;
import com.stars.easyms.schedule.enums.Switch;
import com.stars.easyms.schedule.repository.DistributedScheduleBatchCommitDAO;
import com.stars.easyms.schedule.repository.DistributedScheduleDDLDAO;
import com.stars.easyms.schedule.repository.DistributedScheduleDAO;
import com.stars.easyms.schedule.enums.YesOrNo;
import com.stars.easyms.schedule.exception.DistributedScheduleException;
import com.stars.easyms.schedule.util.ApplicationContextHolder;
import com.stars.easyms.schedule.util.DistributedSchedulePackageUtil;
import com.stars.easyms.schedule.util.DistributedSchedulePropertiesUtil;
import com.stars.easyms.schedule.util.HashUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.BadSqlGrammarException;

import java.sql.SQLSyntaxErrorException;
import java.util.List;
import java.util.Map;

/**
 * 分布式调度框架服务类
 *
 * @author guoguifang
 */
public class DistributedScheduleService {

    private static final Logger logger = LoggerFactory.getLogger(DistributedScheduleService.class);

    private static DistributedScheduleService distributedScheduleService;

    /**
     * 分布式调度框架DDLDAO
     */
    private DistributedScheduleDDLDAO distributedScheduleDDLDAO;

    /**
     * 分布式调度框架DAO
     */
    private DistributedScheduleDAO distributedScheduleDAO;

    /**
     * 得到分布式调度框架的配置参数
     *
     * @param dbScheduleServer 查询条件
     * @return 配置信息
     */
    public DbScheduleServer getDbScheduleServerConfig(DbScheduleServer dbScheduleServer) {
        return getDistributedScheduleDAO().getDbScheduleServerConfig(dbScheduleServer);
    }

    /**
     * 得到分布式调度框架的所有服务器配置参数
     *
     * @return 所有服务器配置信息
     */
    public List<DbScheduleServer> getAllDbScheduleServerConfig() {
        return getDistributedScheduleDAO().getAllDbScheduleServerConfig();
    }

    /**
     * 插入分布式调度框架的配置参数
     * @param dbScheduleServer 配置信息
     * @return 插入成功数量
     */
    public int insertDbScheduleServer(DbScheduleServer dbScheduleServer) {
        return getDistributedScheduleDAO().insertDbScheduleServer(dbScheduleServer);
    }

    /**
     * 分布式调度心跳
     * @param dbScheduleServer 配置信息
     * @return 修改成功数量
     */
    public int heartbeat(DbScheduleServer dbScheduleServer) {
        return getDistributedScheduleDAO().heartbeat(dbScheduleServer);
    }

    /**
     * 修改服务器配置信息
     *
     * @param dbScheduleServer 配置信息
     * @return 修改成功数量
     */
    public int updateDbScheduleServer(DbScheduleServer dbScheduleServer) {
        return getDistributedScheduleDAO().updateDbScheduleServer(dbScheduleServer);
    }

    /**
     * 唤醒现在存活的并且调度器为打开的服务器
     */
    public void signalAliveServerSchedule() {
        DbScheduleServer dbScheduleServer = new DbScheduleServer();
        dbScheduleServer.setScheduleSwitch(Switch.OPEN.getCode());
        dbScheduleServer.setAlive(YesOrNo.YES.getCode());
        dbScheduleServer.setWakeup(YesOrNo.YES.getCode());
        getDistributedScheduleDAO().signalAliveServerSchedule(dbScheduleServer);
    }

    /**
     * 更新已死亡服务器状态
     * @param paramMap 更新参数
     * @return 修改成功数量
     */
    public int updateDeadSeverStatus(Map<String, Object> paramMap) {
        return getDistributedScheduleDAO().updateDeadSeverStatus(paramMap);
    }

    /**
     * 修改分布式调度任务
     *
     * @param updateMap 任务修改参数
     * @return 修改成功数量
     */
    public int updateDbScheduleTaskByIdAndVersion(Map<String, Object> updateMap) {
        return getDistributedScheduleDAO().updateDbScheduleTaskByIdAndVersion(updateMap);
    }

    /**
     * 根据子任务ID修改分布式调度任务
     *
     * @param updateSubTask 任务修改参数
     * @return 修改成功数量
     */
    public int updateDbScheduleSubTaskById(DbScheduleSubTask updateSubTask) {
        return getDistributedScheduleDAO().updateDbScheduleSubTaskById(updateSubTask);
    }

    /**
     * 根据主任务ID删除所有该ID下的子任务
     *
     * @param parentId 主任务ID
     * @return 删除成功数量
     */
    public int deleteDbScheduleSubTaskByParentId(Long parentId) {
        return getDistributedScheduleDAO().deleteDbScheduleSubTaskByParentId(parentId);
    }

    /**
     * 得到所有的任务ID
     * @return 查询结果
     */
    public List<Map<String, String>> getAllTaskId() {
        return getDistributedScheduleDAO().getAllTaskId();
    }

    /**
     * 得到所有的MQConfigId
     * @return 查询结果
     */
    public List<Map<String, String>> getAllMqConfigId() {
        return getDistributedScheduleDAO().getAllMqConfigId();
    }

    /**
     * 得到所有的服务器ID
     * @return 查询结果
     */
    public List<Map<String, String>> getAllServerId() {
        return getDistributedScheduleDAO().getAllServerId();
    }

    /**
     * 得到主任务列表
     * @param paramMap 查询参数
     * @return 查询结果
     */
    public List<DbScheduleTaskView> getViewTask(Map<String, Object> paramMap) {
        return getDistributedScheduleDAO().getViewTask(paramMap);
    }

    /**
     * 根据主任务主键获取对应的主任务详细信息
     * @param id 任务主键
     * @return 查询结果
     */
    public DbScheduleTask getTaskDetailById(Long id) {
        return getDistributedScheduleDAO().getTaskDetailById(id);
    }

    /**
     * 根据主任务主键获取对应的子任务信息
     * @param id 任务主键
     * @return 查询结果
     */
    public List<DbScheduleSubTask> getSubTaskByTaskId(Long id) {
        return getDistributedScheduleDAO().getSubTaskByTaskId(id);
    }

    /**
     * 得到主任务分区数量与子任务数量不符合的任务
     * @param paramMap 查询参数
     * @return 查询结果
     */
    public List<DbScheduleTask> getMismatchTask(Map<String, Object> paramMap) {
        return getDistributedScheduleDAO().getMismatchTask(paramMap);
    }

    /**
     * 得到需要启动MQ监听服务的主任务
     * @param paramMap 查询参数
     * @return 查询结果
     */
    public List<DbScheduleTask> getNeedMQListenerTask(Map<String, Object> paramMap) {
        return getDistributedScheduleDAO().getNeedMQListenerTask(paramMap);
    }

    /**
     * 得到所有子任务已经完成或异常的所有任务
     * @param paramMap 查询参数
     * @return 查询结果
     */
    public List<DbScheduleTask> getFinishedTask(Map<String, Object> paramMap) {
        return getDistributedScheduleDAO().getFinishedTask(paramMap);
    }

    /**
     * 得到已经到执行时间的任务以及失败后需要重试的任务
     * @param paramMap 查询参数
     * @return 查询结果
     */
    public List<DbScheduleTask> getExecutableTask(Map<String, Object> paramMap) {
        return getDistributedScheduleDAO().getExecutableTask(paramMap);
    }

    /**
     * 根据主任务id得到对应的依赖任务
     * @param paramMap 查询参数
     * @return 查询结果
     */
    public List<DbScheduleTask> getDependentTask(Map<String, Object> paramMap) {
        return getDistributedScheduleDAO().getDependentTask(paramMap);
    }

    /**
     * 得到距离当前时间最近的可执行任务的下次执行时间
     * @param paramMap 查询参数
     * @return 查询结果
     */
    public DbScheduleTask getNearestExecutableTask(Map<String, Object> paramMap) {
        return getDistributedScheduleDAO().getNearestExecutableTask(paramMap);
    }

    /**
     * 得到可执行的子任务
     * @param paramMap 查询参数
     * @return 查询结果
     */
    public List<DbScheduleSubTask> getExecutableSubTask(Map<String, Object> paramMap) {
        return getDistributedScheduleDAO().getExecutableSubTask(paramMap);
    }

    /**
     * 当服务器初启动时获取上一次服务器因突然关闭造成的失效子任务
     * @param paramMap 查询参数
     * @return 查询结果
     */
    public List<DbScheduleSubTask> getOutOfWorkSubTask(Map<String, Object> paramMap) {
        return getDistributedScheduleDAO().getOutOfWorkSubTask(paramMap);
    }

    /**
     * 根据taskId查询当前taskId是否已存在
     * @param taskId 任务Id
     * @return 存在数量
     */
    public int checkExistByTaskId(String taskId) {
        return getDistributedScheduleDAO().checkExistByTaskId(taskId);
    }

    /**
     * 插入分布式调度主任务信息
     * @param dbScheduleTask 主任务信息
     * @return 插入成功数量
     */
    public int insertDbScheduleTask(DbScheduleTask dbScheduleTask) {
        return getDistributedScheduleDAO().insertDbScheduleTask(dbScheduleTask);
    }

    /**
     * 修改分布式调度主任务信息
     * @param dbScheduleTask 主任务信息
     * @return 修改成功数量
     */
    public int updateDbScheduleTaskById(DbScheduleTask dbScheduleTask) {
        return getDistributedScheduleDAO().updateDbScheduleTaskById(dbScheduleTask);
    }

    /**
     * 修改分布式调度主任务字段值为Null
     * @param updateMap 修改参数
     * @return 修改成功数量
     */
    public int updateTaskFieldValueToNullById(Map<String, Object> updateMap) {
        return getDistributedScheduleDAO().updateTaskFieldValueToNullById(updateMap);
    }

    /**
     * 插入分布式调度黑白名单信息表
     * @param dbScheduleWhiteBlackList 黑白名单信息
     * @return 插入成功数量
     */
    public int insertDbScheduleWhiteBlackList(DbScheduleWhiteBlackList dbScheduleWhiteBlackList) {
        return getDistributedScheduleDAO().insertDbScheduleWhiteBlackList(dbScheduleWhiteBlackList);
    }

    /**
     * 修改主任务开关
     * @param paramMap 修改参数
     * @return 修改成功数量
     */
    public int updateTaskSwitchById(Map<String, Object> paramMap) {
        return getDistributedScheduleDAO().updateTaskSwitchById(paramMap);
    }

    /**
     * 修改所有主任务开关
     * @param paramMap 修改参数
     * @return 修改成功数量
     */
    public int updateAllTaskSwitch(Map<String, Object> paramMap) {
        return getDistributedScheduleDAO().updateAllTaskSwitch(paramMap);
    }

    /**
     * 插入分布式调度MQ配置信息
     * @param dbScheduleMQConfig MQ配置信息
     * @return 插入成功数量
     */
    public int insertDbScheduleMQConfig(DbScheduleMQConfig dbScheduleMQConfig) {
        return getDistributedScheduleDAO().insertDbScheduleMQConfig(dbScheduleMQConfig);
    }

    /**
     * 修改分布式调度MQ配置信息
     * @param dbScheduleMQConfig MQ配置信息
     * @return 修改成功数量
     */
    public int updateDbScheduleMQConfig(DbScheduleMQConfig dbScheduleMQConfig) {
        return getDistributedScheduleDAO().updateDbScheduleMQConfig(dbScheduleMQConfig);
    }

    /**
     * 查询mqConfigId是否已存在
     * @param mqConfigId MQ配置ID
     * @return 数量
     */
    public long getCountByMqConfigId(String mqConfigId) {
        return getDistributedScheduleDAO().getCountByMqConfigId(mqConfigId);
    }

    /**
     * 得到MQ配置信息
     * @param paramMap 查询参数
     * @return 查询结果
     */
    public List<DbScheduleMQConfig> getViewMQConfig(Map<String, Object> paramMap) {
        return getDistributedScheduleDAO().getViewMQConfig(paramMap);
    }

    /**
     * 插入分布式调度用户配置信息
     * @param dbScheduleUserInfo 用户配置信息
     * @return 插入成功数量
     */
    public int insertDbScheduleUserInfo(DbScheduleUserInfo dbScheduleUserInfo) {
        return getDistributedScheduleDAO().insertDbScheduleUserInfo(dbScheduleUserInfo);
    }

    /**
     * 修改分布式调度用户配置信息
     * @param dbScheduleUserInfo 用户配置信息
     * @return 修改成功数量
     */
    public int updateDbScheduleUserInfo(DbScheduleUserInfo dbScheduleUserInfo) {
        return getDistributedScheduleDAO().updateDbScheduleUserInfo(dbScheduleUserInfo);
    }

    /**
     * 查询分布式调度用户配置信息
     * @param dbScheduleUserInfo 用户名或用户密码
     * @return 查询到的用户信息
     */
    public DbScheduleUserInfo getDbScheduleUserInfo(DbScheduleUserInfo dbScheduleUserInfo) {
        return getDistributedScheduleDAO().getDbScheduleUserInfo(dbScheduleUserInfo);
    }

    /**
     * 插入分布式调度总配置信息
     * @param dbScheduleConfig 总配置信息
     * @return 插入成功数量
     */
    public int insertDbScheduleConfig(DbScheduleConfig dbScheduleConfig) {
        return getDistributedScheduleDAO().insertDbScheduleConfig(dbScheduleConfig);
    }

    /**
     * 修改分布式调度总配置信息
     * @param dbScheduleConfig 总配置信息
     * @return 修改成功数量
     */
    public int updateDbScheduleConfig(DbScheduleConfig dbScheduleConfig) {
        return getDistributedScheduleDAO().updateDbScheduleConfig(dbScheduleConfig);
    }

    /**
     * 查询分布式调度总配置信息
     * @return 查询到的总配置信息
     */
    public DbScheduleConfig getDbScheduleConfig() {
        return getDistributedScheduleDAO().getDbScheduleConfig();
    }

    /**
     * 批量插入子任务数据
     */
    public BatchResult<DbScheduleSubTask> batchInsertDbScheduleSubTask(List<DbScheduleSubTask> list) {
        return DistributedScheduleBatchCommitDAO.getInstance().batchInsert("insertDbScheduleSubTask", list);
    }

    /**
     * 批量插入主任务记录数据
     */
    public BatchResult<DbScheduleTaskRecord> batchInsertDbScheduleTaskRecord(List<DbScheduleTaskRecord> list) {
        return DistributedScheduleBatchCommitDAO.getInstance().batchInsert("insertDbScheduleTaskRecord", list);
    }

    /**
     * 批量插入子任务记录数据
     */
    public BatchResult<DbScheduleSubTaskRecord> batchInsertDbScheduleSubTaskRecord(List<DbScheduleSubTaskRecord> list) {
        return DistributedScheduleBatchCommitDAO.getInstance().batchInsert("insertDbScheduleSubTaskRecord", list);
    }

    /**
     * 批量修改子任务记录数据
     */
    public BatchResult<DbScheduleSubTaskRecord> batchUpdateDbScheduleSubTaskRecord(List<DbScheduleSubTaskRecord> list) {
        return DistributedScheduleBatchCommitDAO.getInstance().batchUpdate("updateDbScheduleSubTaskRecord", list);
    }

    /**
     * 插入分布式调度子任务记录信息
     * @return 插入成功数量
     */
    public int updateDbScheduleTaskRecord() {
        return getDistributedScheduleDAO().updateDbScheduleTaskRecord();
    }

    /**
     * 批量根据ID修改子任务信息
     */
    public BatchResult<DbScheduleSubTask> batchUpdateDbScheduleSubTaskById(List<DbScheduleSubTask> list) {
        return DistributedScheduleBatchCommitDAO.getInstance().batchUpdate("updateDbScheduleSubTaskById", list);
    }

    /**
     * 批量根据ID和版本号修改主任务信息
     */
    public BatchResult<Map<String, Object>> batchUpdateDbScheduleTaskByIdAndVersion(List<Map<String, Object>> list) {
        return DistributedScheduleBatchCommitDAO.getInstance().batchUpdate("updateDbScheduleTaskByIdAndVersion", list);
    }

    /**
     * 根据主任务ID批量删除主任务
     * @param idList 主任务ID列表
     * @return 删除结果
     */
    public BatchResult<Long> batchDeleteDbScheduleTaskById(List<Long> idList) {
        return DistributedScheduleBatchCommitDAO.getInstance().batchDelete("deleteDbScheduleTaskById", idList);
    }

    /**
     * 根据主任务ID批量删除所有该ID下的子任务
     * @param parentIdList 主任务ID列表
     * @return 删除结果
     */
    public BatchResult<Long> batchDeleteDbScheduleSubTaskByParentId(List<Long> parentIdList) {
        return DistributedScheduleBatchCommitDAO.getInstance().batchDelete("deleteDbScheduleSubTaskByParentId", parentIdList);
    }

    /**
     * 初始化表结构及数据
     */
    public void initTable() {
        // 判断表是否存在，若不存在则创建表
        initTable("DB_SCHEDULE_SERVER");
        initTable("DB_SCHEDULE_TASK");
        initTable("DB_SCHEDULE_SUB_TASK");
        initTable("DB_SCHEDULE_WHITE_BLACK_LIST");
        initTable("DB_SCHEDULE_TASK_RECORD");
        initTable("DB_SCHEDULE_SUB_TASK_RECORD");
        initTable("DB_SCHEDULE_MQ_CONFIG");
        initTable("DB_SCHEDULE_USER_INFO");
        initTable("DB_SCHEDULE_CONFIG");

        // 判断管理员用户是否存在，如果不存在则创建管理员用户
        DbScheduleUserInfo dbScheduleUserInfo = new DbScheduleUserInfo();
        dbScheduleUserInfo.setUserName(DistributedScheduleConstants.DEFAULT_USER_NAME);
        dbScheduleUserInfo = getDbScheduleUserInfo(dbScheduleUserInfo);
        if (dbScheduleUserInfo == null) {
            dbScheduleUserInfo = new DbScheduleUserInfo();
            dbScheduleUserInfo.setUserName(DistributedScheduleConstants.DEFAULT_USER_NAME);
            dbScheduleUserInfo.setUserPassword(HashUtil.sha1(HashUtil.md5(DistributedScheduleConstants.DEFAULT_USER_NAME)));
            dbScheduleUserInfo.setPageLayout(DistributedScheduleConstants.PAGE_LAYOUT_LEFT);
            dbScheduleUserInfo.setPageLanguage(DistributedScheduleConstants.PAGE_LANGUAGE_ZH);
            dbScheduleUserInfo.setPageLightup(YesOrNo.YES.getCode());
            dbScheduleUserInfo.setPageTheme(DistributedScheduleConstants.DEFAULT_PAGE_THEME);
            dbScheduleUserInfo.setDashboardContent("");
            insertDbScheduleUserInfo(dbScheduleUserInfo);
        }

        // 判断总配置信息是否存在，如果存在则判断版本号是否相同，如果不存在则创建默认的总配置信息
        DbScheduleConfig dbScheduleConfig = getDbScheduleConfig();
        String currVersion = DistributedSchedulePropertiesUtil.getVersion();
        Integer currVersionIteration = DistributedSchedulePropertiesUtil.getVersionIteration();
        if (dbScheduleConfig == null) {
            dbScheduleConfig = new DbScheduleConfig();
            dbScheduleConfig.setVersion(currVersion);
            dbScheduleConfig.setVersionIteration(currVersionIteration);
            dbScheduleConfig.setOpenLogin(YesOrNo.YES.getCode());
            dbScheduleConfig.setNativeLogin(YesOrNo.YES.getCode());
            dbScheduleConfig.setSessionTimeout(DistributedScheduleConstants.DEFAULT_SESSION_TIMEOUT);
            dbScheduleConfig.setUseSso(YesOrNo.YES.getCode());
            dbScheduleConfig.setRoutePolicy(RoutePolicy.SCRAMBLE.getCode());
            insertDbScheduleConfig(dbScheduleConfig);
        } else {
            Integer oldVersionIteration = dbScheduleConfig.getVersionIteration();
            if (currVersionIteration < oldVersionIteration) {
                logger.error("The current version '" + currVersion + "' of distributed scheduling framework is too low, please upgrade version to '" + dbScheduleConfig.getVersion() + "'!");
            } else if (currVersionIteration > oldVersionIteration) {
                upgrade(oldVersionIteration, currVersionIteration);
            }
        }
    }

    private void initTable(String tableName) {
        try {
            getDistributedScheduleDDLDAO().getCountByTableName(tableName);
        } catch (BadSqlGrammarException badSqlGrammarException) {
            if (badSqlGrammarException.getSQLException() instanceof SQLSyntaxErrorException) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Table '{}' doesn't exist, ready to create...", tableName);
                }
                try {
                    switch (tableName) {
                        case "DB_SCHEDULE_SERVER":
                            getDistributedScheduleDDLDAO().createTable_dbScheduleServer();
                            break;
                        case "DB_SCHEDULE_TASK":
                            getDistributedScheduleDDLDAO().createTable_dbScheduleTask();
                            getDistributedScheduleDDLDAO().alterTable_dbScheduleTask_uix_0();
                            getDistributedScheduleDDLDAO().alterTable_dbScheduleTask_idx_0();
                            getDistributedScheduleDDLDAO().alterTable_dbScheduleTask_idx_1();
                            getDistributedScheduleDDLDAO().alterTable_dbScheduleTask_idx_2();
                            break;
                        case "DB_SCHEDULE_SUB_TASK":
                            getDistributedScheduleDDLDAO().createTable_dbScheduleSubTask();
                            getDistributedScheduleDDLDAO().alterTable_dbScheduleSubTask_uix_0();
                            break;
                        case "DB_SCHEDULE_WHITE_BLACK_LIST":
                            getDistributedScheduleDDLDAO().createTable_dbScheduleWhiteBlackList();
                            break;
                        case "DB_SCHEDULE_TASK_RECORD":
                            getDistributedScheduleDDLDAO().createTable_dbScheduleTaskRecord();
                            break;
                        case "DB_SCHEDULE_SUB_TASK_RECORD":
                            getDistributedScheduleDDLDAO().createTable_dbScheduleSubTaskRecord();
                            getDistributedScheduleDDLDAO().alterTable_dbScheduleSubTaskRecord_idx_0();
                            getDistributedScheduleDDLDAO().alterTable_dbScheduleSubTaskRecord_idx_1();
                            break;
                        case "DB_SCHEDULE_MQ_CONFIG":
                            getDistributedScheduleDDLDAO().createTable_dbScheduleMqConfig();
                            break;
                        case "DB_SCHEDULE_USER_INFO":
                            getDistributedScheduleDDLDAO().createTable_dbScheduleUserInfo();
                            break;
                        case "DB_SCHEDULE_CONFIG":
                            getDistributedScheduleDDLDAO().createTable_dbScheduleConfig();
                            break;
                        default:
                            throw new DistributedScheduleException("Unknown table name '" + tableName + "'!");
                    }
                    if (logger.isInfoEnabled()) {
                        logger.info("Table '{}' create successfully!", tableName);
                    }
                } catch (Exception e) {
                    logger.error("Create table '" + tableName + "' fail, please grant user ddl permissions, or create table manually!", e);
                }
            } else {
                throw badSqlGrammarException;
            }
        }
    }

    private void upgrade(int oldVersionIteration, int currVersionIteration) {
    }

    private DistributedScheduleDDLDAO getDistributedScheduleDDLDAO() {
        if (distributedScheduleDDLDAO == null) {
            distributedScheduleDDLDAO = (DistributedScheduleDDLDAO) ApplicationContextHolder.getApplicationContext().getBean(DistributedSchedulePackageUtil.getDbType().getName() + "DistributedScheduleDDLDAO");
        }
        return distributedScheduleDDLDAO;
    }

    private DistributedScheduleDAO getDistributedScheduleDAO() {
        if (distributedScheduleDAO == null) {
            distributedScheduleDAO = (DistributedScheduleDAO) ApplicationContextHolder.getApplicationContext().getBean(DistributedSchedulePackageUtil.getDbType().getName() + "DistributedScheduleDAO");
        }
        return distributedScheduleDAO;
    }

    public static DistributedScheduleService getInstance() {
        if (distributedScheduleService == null) {
            synchronized (DistributedScheduleService.class) {
                if (distributedScheduleService == null) {
                    distributedScheduleService = new DistributedScheduleService();
                }
            }
        }
        return distributedScheduleService;
    }

    private DistributedScheduleService(){}
}
